﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Media.Animation;
using System.Collections.ObjectModel;
using System.Collections.Specialized;



namespace ScrumFactory.Windows.Helpers {

    /// <summary>
    /// Interaction logic for DockWindowContainer.xaml
    /// </summary>
    public class DockWindowContainer : ContentControl {


        public static readonly DependencyProperty ChildWindowsProperty =
            DependencyProperty.Register("ChildWindows", typeof(ObservableCollection<object>), typeof(DockWindowContainer), new FrameworkPropertyMetadata(null, OnChildWindowsChanged));
        
        #region Constructor

        static DockWindowContainer() {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DockWindowContainer), new FrameworkPropertyMetadata(typeof(DockWindowContainer)));            
        }


        public DockWindowContainer() {
            //NameScope.SetNameScope(this, new NameScope());            
        }

        #endregion

        #region Fields
        
        private Stack<FrameworkElement> childWindows = new Stack<FrameworkElement>();        
        private bool mediatorRegistered = false;

        #endregion

        #region Properties

        /// <summary>
        /// Gets and Sets the Child Dock Windows.
        /// </summary>
        public ObservableCollection<FrameworkElement> ChildWindows {
            get { return (ObservableCollection<FrameworkElement>)GetValue(ChildWindowsProperty); }
            set { SetValue(ChildWindowsProperty, value); }
        }
        
        /// <summary>
        /// Gets and sets the scroll duration in milliseconds.
        /// </summary>
        public double ScrollDuration { get; set; }
            

        /// <summary>
        /// Gets the scrollviewer mediator.
        /// </summary>
        private ScrollViewerOffsetMediator ScrollViewerMediator {
            get {
                ScrollViewerOffsetMediator s = this.Template.FindName("Mediator_PART", this) as ScrollViewerOffsetMediator;
                if (s == null)
                    throw new Exception("DockWindowContainer ControlTemlate should define a Mediator_PART.");
                return s;
            }
        }

        /// <summary>
        /// Gets the windows scroll viewer.
        /// </summary>
        private ScrollViewer WindowScrollViewer {
            get {
                ScrollViewer s = this.Template.FindName("windowScrollViewer_PART", this) as ScrollViewer;
                if (s == null)
                    throw new Exception("DockWindowContainer ControlTemlate should define a windowScrollViewer_PART.");
                return s;
            }
        }


        /// <summary>
        /// Gets the content grid.
        /// </summary>
        private Grid ContentGrid {
            get {
                Grid g = this.Template.FindName("contentGrid_PART", this) as Grid;
                if (g == null)
                    throw new Exception("DockWindowContainer ControlTemlate should define a contentGrid_PART.");
                return g;
            }
        }

        /// <summary>
        /// Gets the width of each new window.
        /// </summary>
        private double NewWindowWidth {
            get {
                return this.ActualWidth;
            }
        }

        #endregion

        #region Methods

        /// <summary>
        /// Creates a new storyboad for the scroll animation.
        /// </summary>
        /// <param name="offset"></param>
        /// <returns></returns>
        private Storyboard CreateStoryBoard(double offset) {
            if(!mediatorRegistered)                
                this.RegisterName(ScrollViewerMediator.Name, ScrollViewerMediator);

            DoubleAnimation myDoubleAnimation = new DoubleAnimation();
            myDoubleAnimation.By = offset;            
            myDoubleAnimation.Duration = new Duration(TimeSpan.FromMilliseconds(ScrollDuration));

            PowerEase ease = new PowerEase();            
            ease.EasingMode = EasingMode.EaseOut;
            myDoubleAnimation.EasingFunction = ease;
            Storyboard.SetTargetName(myDoubleAnimation, ScrollViewerMediator.Name);
            Storyboard.SetTargetProperty(myDoubleAnimation, new PropertyPath(ScrollViewerOffsetMediator.HorizontalOffsetProperty));
            
            
            Storyboard myStoryboard = new Storyboard();
            myStoryboard.Children.Add(myDoubleAnimation);
            if(offset>0)
                myStoryboard.Completed += new EventHandler(Out_Completed);
            else
                myStoryboard.Completed += new EventHandler(In_Completed);
            return myStoryboard;
        }
        
        /// <summary>
        /// Opens a new windos at thios dock window container.
        /// </summary>
        /// <param name="window"></param>
        public void OpenNewWindow(FrameworkElement window) {

            if (window==null)
                return;

            WindowScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;

            ContentGrid.ColumnDefinitions.Last<ColumnDefinition>().Width = new GridLength(NewWindowWidth);

            ColumnDefinition newColumn = new ColumnDefinition();
            newColumn.Width = new GridLength(NewWindowWidth);
            ContentGrid.ColumnDefinitions.Add(newColumn);

            window.SetValue(Grid.ColumnProperty, ContentGrid.ColumnDefinitions.Count - 1);
            ContentGrid.Children.Add(window);

            childWindows.Push(window);

            Storyboard myStoryboard = CreateStoryBoard(NewWindowWidth);               
            myStoryboard.Begin(this, this.Template);

            

        }

        /// <summary>
        /// When the OUT scroll animation ends, adjust the grid columns.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Out_Completed(object sender, EventArgs e) {            
            ContentGrid.ColumnDefinitions[0].Width = new GridLength(0);
            WindowScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
            WindowScrollViewer.ScrollToHorizontalOffset(0);            
            ContentGrid.ColumnDefinitions.Last<ColumnDefinition>().Width = new GridLength(1, GridUnitType.Star);                       
        }

        /// <summary>
        /// When the In scroll animation ends, adjust the grid columns.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void In_Completed(object sender, EventArgs e) {
            FrameworkElement lastWindow = childWindows.Pop();
            ContentGrid.Children.Remove(lastWindow);
            WindowScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled;
            ContentGrid.ColumnDefinitions.Remove(ContentGrid.ColumnDefinitions.Last<ColumnDefinition>());
            ContentGrid.ColumnDefinitions.Last<ColumnDefinition>().Width = new GridLength(1, GridUnitType.Star);

        }

        /// <summary>
        /// Closes the last window of this dock container.
        /// </summary>
        public void CloseLastWindow() {

            if (childWindows.Count == 0)
                return;

            ContentGrid.ColumnDefinitions[0].Width = new GridLength(NewWindowWidth);
            ContentGrid.ColumnDefinitions.Last<ColumnDefinition>().Width = new GridLength(NewWindowWidth, GridUnitType.Pixel);
            WindowScrollViewer.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden;
            WindowScrollViewer.ScrollToHorizontalOffset(NewWindowWidth);

            Storyboard myStoryboard = CreateStoryBoard(-NewWindowWidth);
            myStoryboard.Begin(this, this.Template);


        }

        /// <summary>
        /// Trys to close the window
        /// </summary>
        /// <param name="window"></param>
        public static void CloseWindow(FrameworkElement window) {

            if (window == null)
                return;

            string windowName = window.ToString();
            if (window.Name != null)
                windowName = window.Name;

            DockWindowContainer container = DockWindowContainer.FindDockWindowContainerAncestor(window);
            if (container == null)                
                throw new Exception("Could not find a DockWindowContainer for the window '" + windowName + "'");
            
            if (!container.IsLastWindow(window))
                throw new Exception("Could not close window '" + windowName + "' because it is not the last window at the container");

            container.CloseLastWindow();
        }

        public bool IsLastWindow(FrameworkElement window) {
            return (window == childWindows.Peek());
        }

        private static DockWindowContainer FindDockWindowContainerAncestor(Visual child) {

            DependencyObject parent = VisualTreeHelper.GetParent(child);

            while (parent != null && !(parent is DockWindowContainer)) {
                parent = VisualTreeHelper.GetParent(parent);
            }
            return (parent as DockWindowContainer);
        }



        private static void OnChildWindowsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
            DockWindowContainer container = (DockWindowContainer)d;
            if (container == null)
                return;
            var old = e.OldValue as ObservableCollection<object>;

            if (old != null)
                old.CollectionChanged -= container.OnWorkCollectionChanged;

            var n = e.NewValue as ObservableCollection<object>;

            if (n != null)
                n.CollectionChanged += container.OnWorkCollectionChanged;
        }

        private void OnWorkCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
            if (e.Action == NotifyCollectionChangedAction.Reset) {
                // Clear and update entire collection 
            }

            if (e.NewItems != null) {
                foreach (object w in e.NewItems)                    
                        OpenNewWindow(w as FrameworkElement);
            }

            if (e.OldItems != null) {
                foreach (object w in e.OldItems)
                    if (IsLastWindow(w as FrameworkElement))
                        CloseLastWindow();
            }
        }


        #endregion


    }

    /// <summary>
    /// Mediator that forwards Offset property changes on to a ScrollViewer
    /// instance to enable the animation of Horizontal/VerticalOffset.
    /// </summary>
    public class ScrollViewerOffsetMediator : FrameworkElement {
        /// <summary>
        /// ScrollViewer instance to forward Offset changes on to.
        /// </summary>
        public ScrollViewer ScrollViewer {
            get { return (ScrollViewer)GetValue(ScrollViewerProperty); }
            set { SetValue(ScrollViewerProperty, value); }
        }
        public static readonly DependencyProperty ScrollViewerProperty =
            DependencyProperty.Register(
                "ScrollViewer",
                typeof(ScrollViewer),
                typeof(ScrollViewerOffsetMediator),
                new PropertyMetadata(OnScrollViewerChanged));

        private static void OnScrollViewerChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
            var mediator = (ScrollViewerOffsetMediator)o;
            var scrollViewer = (ScrollViewer)(e.NewValue);
            if (null != scrollViewer) {
                scrollViewer.ScrollToVerticalOffset(mediator.VerticalOffset);
            }
        }

        /// <summary>
        /// VerticalOffset property to forward to the ScrollViewer.
        /// </summary>
        public double VerticalOffset {
            get { return (double)GetValue(VerticalOffsetProperty); }
            set { SetValue(VerticalOffsetProperty, value); }
        }
        public static readonly DependencyProperty VerticalOffsetProperty =
            DependencyProperty.Register(
                "VerticalOffset",
                typeof(double),
                typeof(ScrollViewerOffsetMediator),
                new PropertyMetadata(OnVerticalOffsetChanged));
        public static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
            var mediator = (ScrollViewerOffsetMediator)o;
            if (null != mediator.ScrollViewer) {
                mediator.ScrollViewer.ScrollToVerticalOffset((double)(e.NewValue));
            }
        }

        public double HorizontalOffset {
            get { return (double)GetValue(HorizontalOffsetProperty); }
            set { SetValue(HorizontalOffsetProperty, value); }
        }
        public static readonly DependencyProperty HorizontalOffsetProperty =
            DependencyProperty.Register(
                "HorizontalOffset",
                typeof(double),
                typeof(ScrollViewerOffsetMediator),
                new PropertyMetadata(OnHorizontalOffsetChanged));

        public static void OnHorizontalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) {
            var mediator = (ScrollViewerOffsetMediator)o;
            if (null != mediator.ScrollViewer) {
                mediator.ScrollViewer.ScrollToHorizontalOffset((double)(e.NewValue));
            }

        }
    }
}
